﻿using System.ComponentModel;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace K9Nano.AspNetCore.Filters;

public class K9ActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        ApiLoggingWrapper? loggingWrapper = null;
        var loggingAttr = context.Filters.OfType<RequestLoggingAttribute>().LastOrDefault();
        if (loggingAttr?.Enabled == true)
        {
            var descaAttr = context.ActionDescriptor.EndpointMetadata.LastOrDefault(a => a is DescriptionAttribute) as DescriptionAttribute;
            var displayName = descaAttr?.Description;
            string controllerName, actrionName;
            if (context.ActionDescriptor is ControllerActionDescriptor actionDescriptor)
            {
                actrionName = actionDescriptor.ActionName;
                controllerName = actionDescriptor.ControllerTypeInfo.FullName!;
            }
            else
            {
                controllerName = context.Controller.GetType().FullName!;
                actrionName = context.ActionDescriptor.DisplayName!;
            }
            loggingWrapper = new ApiLoggingWrapper(controllerName, actrionName, displayName, context.ActionArguments);
        }

        if (!context.ModelState.IsValid)
        {
            if (context.Filters.OfType<ApiControllerAttribute>().Any())
            {
                var error = GetErrors(context.ModelState);
                var result = new ApiResponse(400, "参数非法") { Result = error };
                if (loggingWrapper is not null)
                {
                    loggingWrapper.Result = result;
                }
                context.Result = new JsonResult(result)
                {
                    StatusCode = 400
                };
            }
        }

        if (loggingWrapper is not null)
        {
            context.HttpContext.Items[ApiLoggingWrapper.Name] = loggingWrapper;
        }
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        if (context.Canceled || context.Exception != null)
        {
            return;
        }

        switch (context.Result)
        {
            case JsonResult jsonResult:
                {
                    if (jsonResult.Value is ApiResponse apiResponse)
                    {
                        if (apiResponse.Code == 400)
                        {
                            context.HttpContext.Response.StatusCode = 400;
                        }
                    }
                    else
                    {
                        jsonResult.Value = ApiResponse.FromDto(jsonResult.Value!);
                    }
                }
                break;
            case ObjectResult objectResult:
                {
                    if (objectResult.Value is ApiResponse apiResponse)
                    {
                        if (apiResponse.Code == 400)
                        {
                            context.HttpContext.Response.StatusCode = 400;
                        }
                    }
                    else
                    {
                        context.Result = new JsonResult(objectResult.Value == null
                            ? ApiResponse.Ok
                            : ApiResponse.FromDto(objectResult.Value));
                    }
                }
                break;
            case EmptyResult _:
                context.Result = new JsonResult(ApiResponse.Ok);
                break;
        }

        if (context.HttpContext.Items[ApiLoggingWrapper.Name] is ApiLoggingWrapper loggingWrapper)
        {
            if (context.Result is JsonResult jsonResult)
            {
                loggingWrapper.Result = jsonResult.Value;
            }
        }
    }

    private static Dictionary<string, string[]> GetErrors(ModelStateDictionary modelState)
    {
        var errors = modelState
            .Where(x => x.Value?.Errors.Count > 0)
            .ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value?.Errors.Select(e => e.ErrorMessage).ToArray() ?? []
            );
        return errors;
    }
}
